using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;

using NoMana.Components.Camera;

namespace NoMana.Components.DeferredShader
{
    public class DeferredRenderer : Microsoft.Xna.Framework.DrawableGameComponent
    {
        private GameCamera camera;
        private QuadRenderComponent quadRenderer;
        Scene scene;
        private RenderTarget2D colorRT;    //this Render Target will hold color and Specular Intensity
        private RenderTarget2D normalRT; //this Render Target will hold normals and Specular Power
        private RenderTarget2D depthRT; //finally, this one will hold the depth
        Effect clearBufferEffect;
        private SpriteBatch spriteBatch;
        private Vector2 halfPixel;
        private Effect directionalLightEffect;
        private RenderTarget2D lightRT;
        private Effect finalCombineEffect;
        private Effect pointLightEffect;
        private bool _isInitialized = false;

        public DeferredRenderer(Game game, GameCamera gameCamera)
            : base(game)
        {
            scene = new Scene(game);
            camera = gameCamera;

            Initialize();
        }
        public override void Initialize()
        {
            quadRenderer = new QuadRenderComponent(Game);
            _isInitialized = true;

            base.Initialize();
            
        }
        protected override void LoadContent()
        {
            halfPixel.X = 0.5f / (float)GraphicsDevice.PresentationParameters.BackBufferWidth;
            halfPixel.Y = 0.5f / (float)GraphicsDevice.PresentationParameters.BackBufferHeight;

            //get the sizes of the backbuffer, in order to have matching render targets   
            int backBufferWidth = GraphicsDevice.PresentationParameters.BackBufferWidth;
            int backBufferHeight = GraphicsDevice.PresentationParameters.BackBufferHeight;

            colorRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, true, SurfaceFormat.Color, DepthFormat.None);
            normalRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, true, SurfaceFormat.Color, DepthFormat.None);
            depthRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, true, SurfaceFormat.Single, DepthFormat.None);
            lightRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, true, SurfaceFormat.Color, DepthFormat.None);
            scene.InitializeScene();
            clearBufferEffect = Game.Content.Load<Effect>("Effects\\DeferredShader\\ClearGBuffer");
            directionalLightEffect = Game.Content.Load<Effect>("Effects\\DeferredShader\\DirectionalLight");
            finalCombineEffect = Game.Content.Load<Effect>("Effects\\DeferredShader\\CombineFinal");
            pointLightEffect = Game.Content.Load<Effect>("Effects\\DeferredShader\\PointLight");

            spriteBatch = new SpriteBatch(Game.GraphicsDevice);
            base.LoadContent();
        }
        public override void Draw(GameTime gameTime)
        {

            if (_isInitialized)
            {
                SetGBuffer();
                ClearGBuffer();
                scene.DrawScene(camera, gameTime);
                ResolveGBuffer();
                DrawLights(gameTime);
                base.Draw(gameTime);
            }
        }
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        private void SetGBuffer()
        {
            GraphicsDevice.SetRenderTarget(colorRT);
            GraphicsDevice.SetRenderTarget(normalRT);
            GraphicsDevice.SetRenderTarget(depthRT);
        }
        private void ResolveGBuffer()
        {
            //set all rendertargets to null. In XNA 2.0, switching a rendertarget causes the resolving of the previous rendertarget.
            // In XNA 1.0, we needed to call GraphicsDevice.ResolveRenderTarget(i);
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.SetRenderTarget(null);
        }

        private void ClearGBuffer()
        {
            clearBufferEffect.Techniques[0].Passes[0].Apply();
            quadRenderer.Render(Vector2.One * -1, Vector2.One);
        }

        private void DrawDirectionalLight(Vector3 lightDirection, Color color)
        {
            //set all parameters
            directionalLightEffect.Parameters["colorMap"].SetValue(colorRT);
            directionalLightEffect.Parameters["normalMap"].SetValue(normalRT);
            directionalLightEffect.Parameters["depthMap"].SetValue(depthRT);
            directionalLightEffect.Parameters["lightDirection"].SetValue(lightDirection);
            directionalLightEffect.Parameters["Color"].SetValue(color.ToVector3());
            directionalLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
            directionalLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.ViewMatrix * camera.ProjectionMatrix));
            directionalLightEffect.Parameters["halfPixel"].SetValue(halfPixel);

            directionalLightEffect.Techniques[0].Passes[0].Apply();
            //draw a full-screen quad
            quadRenderer.Render(Vector2.One * -1, Vector2.One);
        }

        private void DrawLights(GameTime gameTime)
        {
            GraphicsDevice.SetRenderTarget(lightRT);

            //clear all components to 0
            GraphicsDevice.Clear(Color.Transparent);
            //GraphicsDevice.RenderState.AlphaBlendEnable = true;
            ////use additive blending, and make sure the blending factors are as we need them
            //GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
            //GraphicsDevice.RenderState.SourceBlend = Blend.One;
            //GraphicsDevice.RenderState.DestinationBlend = Blend.One;
            ////use the same operation on the alpha channel
            //GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = false;
            //GraphicsDevice.RenderState.DepthBufferEnable = false;

            DrawDirectionalLight(new Vector3(0, -1f, 1), Color.DimGray);
            Color[] colors = new Color[10];
            colors[0] = Color.ForestGreen;
            colors[1] = Color.Blue;
            colors[2] = Color.Pink;
            colors[3] = Color.Yellow;
            colors[4] = Color.Orange;
            colors[5] = Color.Green;
            colors[6] = Color.Crimson;
            colors[7] = Color.CornflowerBlue;
            colors[8] = Color.Gold;
            colors[9] = Color.Honeydew;

            float angle = (float)gameTime.TotalGameTime.TotalSeconds;
            for (int i = 0; i < 10; i++)
            {
                Vector3 pos = new Vector3((float)Math.Sin(i * MathHelper.TwoPi / 10 + gameTime.TotalGameTime.TotalSeconds), 0.3f,
                                                               (float)Math.Cos(i * MathHelper.TwoPi / 10 + gameTime.TotalGameTime.TotalSeconds));
                DrawPointLight(pos * 20, colors[i], 12, 2);

                pos = new Vector3((float)Math.Cos(i * MathHelper.TwoPi / 10 + gameTime.TotalGameTime.TotalSeconds), -0.6f,
                                                  (float)Math.Sin(i * MathHelper.TwoPi / 10 + gameTime.TotalGameTime.TotalSeconds));
                DrawPointLight(pos * 20, colors[i], 20, 1);
            }
            DrawPointLight(new Vector3(0, (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds * 0.8) * 40, 0), Color.Red, 30, 5);
            DrawPointLight(new Vector3(0, 0, 70), Color.Wheat, 55 + 10 * (float)Math.Sin(5 * gameTime.TotalGameTime.TotalSeconds), 3);
            
            //GraphicsDevice.RenderState.AlphaBlendEnable = false;
            GraphicsDevice.SetRenderTarget(null);

            //set the effect parameters
            finalCombineEffect.Parameters["colorMap"].SetValue(colorRT);
            finalCombineEffect.Parameters["lightMap"].SetValue(lightRT);
            finalCombineEffect.Parameters["halfPixel"].SetValue(halfPixel);

            finalCombineEffect.Techniques[0].Passes[0].Apply();
            //render a full-screen quad
            quadRenderer.Render(Vector2.One * -1, Vector2.One);

        }

        private void DrawPointLight(Vector3 lightPosition, Color color, float lightRadius, float lightIntensity)
        {
            //set the G-Buffer parameters
            pointLightEffect.Parameters["colorMap"].SetValue(colorRT);
            pointLightEffect.Parameters["normalMap"].SetValue(normalRT);
            pointLightEffect.Parameters["depthMap"].SetValue(depthRT);

            //compute the light world matrix
            //scale according to light radius, and translate it to light position
            Matrix sphereWorldMatrix = Matrix.CreateScale(lightRadius) * Matrix.CreateTranslation(lightPosition);
            pointLightEffect.Parameters["World"].SetValue(sphereWorldMatrix);
            pointLightEffect.Parameters["View"].SetValue(camera.ViewMatrix);
            pointLightEffect.Parameters["Projection"].SetValue(camera.ProjectionMatrix);
            //light position
            pointLightEffect.Parameters["lightPosition"].SetValue(lightPosition);

            //set the color, radius and Intensity
            pointLightEffect.Parameters["Color"].SetValue(color.ToVector3());
            pointLightEffect.Parameters["lightRadius"].SetValue(lightRadius);
            pointLightEffect.Parameters["lightIntensity"].SetValue(lightIntensity);

            //parameters for specular computations
            pointLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
            pointLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.ViewMatrix * camera.ProjectionMatrix));
            //size of a halfpixel, for texture coordinates alignment
            pointLightEffect.Parameters["halfPixel"].SetValue(halfPixel);
            //calculate the distance between the camera and light center
            float cameraToCenter = Vector3.Distance(camera.Position, lightPosition);
            //if we are inside the light volume, draw the sphere's inside face
            //if (cameraToCenter < lightRadius)
            //    GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
            //else
            //    GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            
            pointLightEffect.Techniques[0].Passes[0].Apply();

            //GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
        }



    }
}